#!/usr/local/develop/python/bin/python
################################################################################
# pop3d.py
#
# UTF8SMTP NIDAMail Program
# Version 1.0
# Date : July 23, 2008
# Homepage: http://sourceforge.net/projects/utf8smtp/
################################################################################
import threading, time, os
from socket import *

# Configuration
import common
conf = common.conf
vh = common.vh
access = common.access

################################################################################
# Defining Functions for eai-pop3
# Load configuration from common.py
################################################################################
def receivePOP3(conn, addr):
	conn.settimeout(conf['POP3D_TIMEOUT'])
	conn.sendall('+OK POP3__ %s\r\n' % common.PROGRAM_VERSION)
	common.printLOG('Connected from %s:%d(%d)' % (addr[0], addr[1], threading.activeCount()), 'pop3d')

	mail = {'USER':'', 'PATH':'', 'COUNT':0, 'SIZE':0, 'STATE':'USER'}

	buffStr = ''
	while mail['STATE'] != 'QUIT':
		try:
			recvStr = conn.recv(1024)
			if recvStr == '':
				common.printLOG('Disconnected from %s:%d(%s) by client.' % (addr[0], addr[1], mail['USER']), 'pop3d')
				break
		except:
			common.printLOG('Disconnected from %s:%d(%s) by client.' % (addr[0], addr[1], mail['USER']), 'pop3d')
			break
		else:
			buffStr = buffStr + recvStr

### line process
		if buffStr.find('\n') != -1:
			i = 0
			listStr = buffStr.splitlines()
			for lineStr in listStr:
				i = i + 1
				if not buffStr.endswith('\n') and i == len(listStr): break
				cmdStr = lineStr.upper()

				common.printLOG('%s(%s)' % (lineStr, mail['USER']), 'pop3d')
				if cmdStr.startswith('USER') and mail['STATE'] == 'USER':
					mail['USER'] = common.makeEMAIL(lineStr[5:])
					mail['USER'] = common.convertIDN(mail['USER'])
					mail['PATH'] = common.searchUserPath(mail['USER'])[0]

					if mail['PATH'] == 'no domain':
						conn.sendall('-ERR It is not local domain\r\n')
						common.printLOG('-ERR It is not local domain(%s)' % mail['USER'], 'pop3d')
					elif mail['PATH'] == 'no user':
						conn.sendall('-ERR No such user\r\n')
						common.printLOG('-ERR No such ueser(%s)' % mail['USER'], 'pop3d')
					else:
						conn.sendall('+OK User name accepted, password please\r\n')
						mail['STATE'] = 'PASS'

				elif cmdStr.startswith('PASS') and mail['STATE'] == 'PASS':
					if common.iscorrect_password(mail['USER'], lineStr[5:]) == 1:
						try:
							connDB = common.connectDB('%s/.sqlite' % mail['PATH'])
							curDB = connDB.cursor()
						except:
							conn.sendall('-ERR Bad login\r\n')
							common.printLOG("pop3d   | Can't connect to %s" % mail['PATH'], 'error')
						else:
							try:
								sqlwhere = ''
								curDB.execute("select count(*), ifnull(sum(size), 0) from maillist where mbox='' or mbox='recv'%s" % sqlwhere)
								result = curDB.fetchone()
								mail['COUNT'] = int(result[0])
								mail['SIZE'] = int(result[1])
								sqllimit = ''
								curDB.execute("select key19, size from maillist where mbox='' or mbox='recv'%s order by key19 DESC%s" % (sqlwhere, sqllimit))
								maillist = curDB.fetchall()
								curDB.close()
								connDB.close()
								conn.sendall('+OK Mailbox open, %d messages.\r\n' % mail['COUNT'])
								mail['STATE'] = 'AUTH'
							except:
								conn.sendall('-ERR Bad login\r\n')
								common.printLOG('DB Error - PASS(%s)' % mail['USER'], 'pop3d')
					else:
						conn.sendall('-ERR Bad login\r\n')
						common.printLOG('password incorrect(%s, %s)' % (mail['USER'], lineStr[5:]), 'pop3d')

				elif cmdStr.startswith('STAT') and mail['STATE'] == 'AUTH': conn.sendall('+OK %d %d\r\n' % (mail['COUNT'], mail['SIZE']))

				elif cmdStr.startswith('LIST') and mail['STATE'] == 'AUTH':
					if lineStr[5:] == '':
						mno = 1
						conn.sendall('+OK Mailbox scan listing follows\r\n')
						for row in maillist:
							conn.sendall('%d %s\r\n' % (mno, maillist[mno-1][1]))
							mno += 1
						conn.sendall('.\r\n')
					else:
						try: mno = int(lineStr[5:])
						except:
							conn.sendall('-ERR No such message\r\n')
							common.printLOG('-ERR No such message(%s)' % mail['USER'], 'pop3d')
						else:
							if mno > mail['COUNT']:
								conn.sendall('-ERR No such message\r\n')
								common.printLOG('-ERR No such message(%s)' % mail['USER'], 'pop3d')
							else: conn.sendall('+OK %d %s\r\n' % (mno, maillist[mno-1][1]))

				elif cmdStr.startswith('UIDL') and mail['STATE'] == 'AUTH':
					if lineStr[5:] == '':
						mno = 1
						conn.sendall('+OK Unique-ID listing follows\r\n')
						for row in maillist:
							conn.sendall('%d %s\r\n' % (mno, maillist[mno-1][0]))
							mno += 1
						conn.sendall('.\r\n')
					else:
						try: mno = int(lineStr[5:])
						except:
							conn.sendall('-ERR No such message\r\n')
							common.printLOG('-ERR No such message(%s)' % mail['USER'], 'pop3d')
						else:
							if mno > mail['COUNT']:
								conn.sendall('-ERR No such message\r\n')
								common.printLOG('-ERR No such message(%s)' % mail['USER'], 'pop3d')
							else: conn.sendall('+OK %d %s\r\n' % (mno, maillist[mno-1][0]))

				elif cmdStr.startswith('TOP') and mail['STATE'] == 'AUTH':
					if lineStr[4:] == '':
						conn.sendall('-ERR No such message\r\n')
						common.printLOG('-ERR No such message(%s)' % mail['USER'], 'pop3d')
					else:
						try: mno = int(lineStr[4:].strip().split()[0])
						except:
							conn.sendall('-ERR No such message\r\n')
							common.printLOG('-ERR No such message(%s)' % mail['USER'], 'pop3d')
						else:
							if mno > mail['COUNT']:
								conn.sendall('-ERR No such message\r\n')
								common.printLOG('-ERR No such message(%s)' % mail['USER'], 'pop3d')
							else:
								try: f = open('%/recv/%s' % (mail['PATH'], maillist[mno-1][0]), 'r')
								except:
									conn.sendall('+OK 0 octets\r\n')
									conn.sendall('\r\n.\r\n')
								else:
									conn.sendall('+OK %s octets\r\n' % (maillist[mno-1][1]))
									conn.sendall(f.read())
									f.close()
									conn.sendall('\r\n.\r\n')

				elif cmdStr.startswith('RETR') and mail['STATE'] == 'AUTH':
					if lineStr[5:] == '':
						conn.sendall('-ERR No such message\r\n')
						common.printLOG('-ERR No such message(%s)' % mail['USER'], 'pop3d')
					else:
						try: mno = int(lineStr[5:])
						except:
							conn.sendall('-ERR No such message\r\n')
							common.printLOG('-ERR No such message(%s)' % mail['USER'], 'pop3d')
						else:
							if mno > mail['COUNT']:
								conn.sendall('-ERR No such message\r\n')
								common.printLOG('-ERR No such message(%s)' % mail['USER'], 'pop3d')
							else:
								try: f = open('%s/recv/%s' % (mail['PATH'], maillist[mno-1][0]), 'r')
								except:
									conn.sendall('+OK 0 octets\r\n')
									conn.sendall('\r\n.\r\n')
								else:
									conn.sendall('+OK %s octets\r\n' % (maillist[mno-1][1]))
									conn.sendall(f.read())
									f.close()
									conn.sendall('\r\n.\r\n')

				elif cmdStr.startswith('DELE') and mail['STATE'] == 'AUTH':
					if lineStr[5:] == '':
						conn.sendall('-ERR No such message\r\n')
						common.printLOG('-ERR No such message(%s)' % mail['USER'], 'pop3d')
					else:
						try: mno = int(lineStr[5:])
						except:
							conn.sendall('-ERR No such message\r\n')
							common.printLOG('-ERR No such message(%s)' % mail['USER'], 'pop3d')
						else:
							if mno > mail['COUNT']: conn.sendall('-ERR No such message\r\n')
							else:
								try:
									connDB = common.connectDB('%s/.sqlite' % mail['PATH'])
									curDB = connDB.cursor()
								except: printLOG("pop3d   | Can't connect to %s" % mail['PATH'], 'error')
								else:
									try:
										curDB.execute("select filename from attachlist where key19_first='%s'" % maillist[mno-1][0])
										result = curDB.fetchall()
										curDB.execute("delete from maillist where key19='%s'" % maillist[mno-1][0])
										curDB.execute("delete from receiverlist where key19_first='%s'" % maillist[mno-1][0])
										curDB.execute("delete from attachlist where key19_first='%s'" % maillist[mno-1][0])
										curDB.execute("delete from rfc822list where key19_first='%s'" % maillist[mno-1][0])
										connDB.commit()
										common.closeDB(connDB, curDB)
									except: common.printLOG('DB Error - DELE(%s)' % mail['USER'], 'pop3d')
									else:
										nowsize = common.get_nowsize(mail['USER'])
										if nowsize >= 0: common.update_nowsize(mail['USER'], nowsize)
										try:
											if os.path.exists('%s/recv/%s' % (mail['PATH'], maillist[mno-1][0])): os.unlink('%s/recv/%s' % (mail['PATH'], maillist[mno-1][0]))
											for attach in result:
												if os.path.exists('%s/recv/%s' % (mail['PATH'], attach[0])): os.unlink('%s/recv/%s' % (mail['PATH'], attach))
										except: common.printLOG('Unlink Error - DELE(%s)' % mail['USER'], 'pop3d')
								conn.sendall('+OK Message deleted\r\n')

				elif cmdStr.startswith('NOOP'): conn.sendall('+OK NOOP\r\n')

				elif cmdStr.startswith('AUTH'):
					conn.sendall('-ERR Unknown command\r\n')
					common.printLOG('-ERR Unknown command(%s)' % mail['USER'], 'pop3d')

				elif cmdStr.startswith('CAPA'):
					conn.sendall('-ERR Unknown command\r\n')
					common.printLOG('-ERR Unknown command(%s)' % mail['USER'], 'pop3d')

				elif cmdStr.startswith('QUIT'):
					conn.sendall('+OK Closing connection\r\n')
					mail['STATE'] = 'QUIT'
					break

				else:
					conn.sendall('-ERR Closing connection\r\n')
					common.printLOG('-ERR Closing connection(%s)' % mail['USER'], 'pop3d')
					mail['STATE'] = 'QUIT'
					break

			if buffStr.endswith('\n'): buffStr = ''
			else: buffStr = lineStr
	conn.close()
	common.printLOG('Disconnected from %s:%d(%s)' % (addr[0], addr[1], mail['USER']), 'pop3d')

### MAIN
common.printLOG('pop3d   | POP3D is started', 'common')
try:
	svrsock = socket(AF_INET, SOCK_STREAM)
	svrsock.setsockopt(SOL_SOCKET, SO_REUSEADDR, 1)
	svrsock.bind(('', conf['POP3D_PORT']))
	svrsock.listen(conf['POP3D_MAX_THREADS']+1)
except:
	common.printLOG('pop3d   | Port: %s - already in use' % (conf['POP3D_PORT']), 'error')
	common.printLOG('pop3d   | Port: %s - already in use' % (conf['POP3D_PORT']), 'console')
	os.sys.exit(-1)
common.setUIDGID('pop3d')

while 1:
	conn, addr = svrsock.accept()
	common.loadConf('pop3d')
	common.loadVH('pop3d')
	common.loadAccess('pop3d')
	if conf['POP3D_TYPE'] == 'fork':
		pid = os.fork()
		if pid == 0:
			receivePOP3(conn, addr)
			break
		else:
			th = threading.Thread(target=common.waitPID, args=())
			th.start()
			conn.close()
		while(threading.activeCount() > conf['POP3D_MAX_THREADS']):
			common.printLOG('pop3d  | fork count is larger than %s' % conf['POP3D_MAX_THREADS'], 'error')
			time.sleep(1)
	else:
		th = threading.Thread(target=receivePOP3, args=(conn, addr))
		th.start()
		while(threading.activeCount() > conf['POP3D_MAX_THREADS']):
			common.printLOG('pop3d   | thread count is larger than %s' % conf['POP3D_MAX_THREADS'], 'error')
			time.sleep(1)